package ru.ifmo.thesis.task;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.thesis.domain.AbstractGeoService;
import ru.ifmo.thesis.domain.ContractOptions;
import ru.ifmo.thesis.domain.GeoServiceFactory;
import ru.ifmo.thesis.domain.Job;
import ru.ifmo.thesis.domain.JobFactory;
import ru.ifmo.thesis.domain.Location;
import ru.ifmo.thesis.domain.Worker;
import ru.ifmo.thesis.quantities.Percent;
import ru.ifmo.thesis.util.MyArrays;

public class TaskGenerator {
	
	private static final Logger logger = LoggerFactory.getLogger(TaskGenerator.class);
	
	private static AbstractGeoService geoService = GeoServiceFactory.getGeoService();
	
	private static Random random = new Random();

	public Task generateTask() {
		return generateTask(new TaskGenerationOptions());
	}
	
	public Task generateTask(TaskGenerationOptions options) {
		return new Task(getWorkers(options), getJobList(options));
	}
	
	public DOPTask generateDOPTask(TaskGenerationOptions options, Percent slicePercent) {
		List<Job> jobList = getJobList(options);
		int slicePoint = (int) (jobList.size() * slicePercent.value());
		List<Job> initialPopulation = new ArrayList<Job>(jobList.subList(0, slicePoint));
		List<Job> jobsToAdd = new ArrayList<Job>(jobList.subList(slicePoint, jobList.size()));
		
		return new DOPTask(getWorkers(options), initialPopulation, jobsToAdd);
	}
	
	private static List<Worker> getWorkers(TaskGenerationOptions options) {
		logger.info("Generating workers...");
		List<Worker> result = new ArrayList<Worker>();
		
		for (int i = 0; i < options.getWorkersCount(); i++) {
			result.add(new Worker("Worker #" + i));
		}
		
		logger.info("Generating workers done");
		return result;
	}
	
	private static List<Job> getJobList(TaskGenerationOptions options) {
		logger.info("Generating tasks...");
		List<Job> result = new ArrayList<Job>();
	
		Location[] locations = generateLocations(options);
		
		ContractOptions[] contracts = generateContracts(options);
		
		for (int i = 0; i < options.getTaskGenerationCount(); i++) {
			int eliminationTime = 30 * (2 + random.nextInt(10));
			
			Job newTask = JobFactory.newJob(("Description #" + i), MyArrays.getRandom(locations), new Date(), MyArrays.getRandom(contracts),
			eliminationTime);
			
			result.add(newTask);
			logger.debug("New task: " + newTask);
		}
		
		logger.info("Generating tasks done");
		return result;
	}
	
	private static Location[] generateLocations(TaskGenerationOptions options) {
		int count = options.getGenerateLocationsCount();
		int radius = options.getLocationGenRadius();
		Location officeLocation = geoService.getOfficeLocation();
		
        logger.info("Generating locations...");
		Location[] locations = new Location[count];
		
		for (int i = 0; i < count; i++) {
			
			Location newLocation = null;
			do {
				newLocation = geoService.getLocation(
						"Object #" + i, 
						random.nextDouble() * radius, 
						random.nextDouble() * radius
				);
			} while (geoService.distance(officeLocation, newLocation).value() > radius);
			
			logger.debug("New location: " + newLocation);
			locations[i] = newLocation;
		}
		
		logger.info("Generating locations done");
		return locations;
	}
	
	private static ContractOptions[] generateContracts(TaskGenerationOptions options) {
		int numberOfContracts = options.getGenerateContractsCount();
		
		logger.info("Generating contracts...");
		ContractOptions[] result = new ContractOptions[numberOfContracts];
	
		for (int i = 0; i < numberOfContracts; i++) {
			int minutes = 30 * (2 + random.nextInt(options.getMaxFreePeriod()));
			double intialPenalty = 1 + random.nextInt(options.getMaxPenalty());
			double factor = 1 + random.nextInt(options.getMaxFactor());
			result[i] = new ContractOptions(minutes, intialPenalty, factor);
			logger.debug("New contract: " + result[i]);
		}
		
		logger.info("Generating contracts done");
		return result;
	}
	
}
